package com.ys.day30.p1回顾升级;

import com.ys.day30.p2注解与反射.MyAnnotation;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * 描述:
 * P2自定义反射工具类
 *
 * @author CodeYang
 * @version 2021/1/12 8:57
 */
final public class ReflectUtils {

    /**
     * 工具类的 构造方法一般为私有
     */
    private ReflectUtils() {
        throw new RuntimeException("不要new 我");
    }

    /**
     * *1通过反射创建对象
     * * 1 根据参数获取Class对象
     * * 2 获取构造器对象
     * * 3 调用newInstance()方法，创建对象
     * * @param className 以字符串表示的类全名(包名称+类名称)
     * 通过反射创建对象 --通过 全类名
     *
     * @param className 全类名
     * @param <T>       支持泛型
     * @return 创建的对象
     * @throws Exception
     */
    public static <T> T creteObject(String className) throws Exception {
        Class<?> aClass = Class.forName(className);
        Constructor<?> constructor = aClass.getDeclaredConstructor();

        return (T) constructor.newInstance();
    }

    /**
     * 2通过反射创建对象 通过Class对象创建对象
     *
     * @param classz 字节码对应的文件
     * @param <T>    支持泛型
     * @return 创建的对象
     * @throws Exception
     */
    public static <T> T creteObject(Class<T> classz) throws Exception {
        Class<T> classz1 = classz;
        Constructor<?> constructor = classz1.getDeclaredConstructor();
        return (T) constructor.newInstance();
    }

    /**
     * 3通过class全类名 调用有参构造方法创建对象
     *
     * @param className 全类名
     * @param values    入参值
     * @param params    参数类型列表
     * @param <T>       支持泛型
     * @return 被创建的对象
     * @throws Exception
     */
    public static <T> T creteObject(String className, Object[] values, Class... params) throws Exception {
        Class<?> aClass = Class.forName(className);
        Constructor<?> constructor = aClass.getDeclaredConstructor(params);
        return (T) constructor.newInstance(values);
    }


    /**
     * 4通过class对象反射 调用有参构造方法创建对象
     *
     * @param className 全类名
     * @param values    入参值
     * @param params    参数类型列表
     * @param <T>       支持泛型
     * @return 被创建的对象
     * @throws Exception
     */
    public static <T> T creteObject(Class<T> classz, Object[] values, Class... params) throws Exception {
        Class<T> aClass = classz;
        Constructor<?> constructor = aClass.getDeclaredConstructor(params);
        return (T) constructor.newInstance(values);
    }


    /**
     * 5 通过反射调用 调用无参方法,返回调用方法的结果
     * 步骤：
     * 1 根据方法名称获取对应的方法签名Method对象
     * 2 调用Method对象的invoke()方法完成方法调用，并且返回方法调用的结果
     *
     * @param obj        调用对象
     * @param methodName 方法名称
     * @param <T>        参数是一个泛型对象
     * @param <R>        返回类型 支持的泛型  (返回一个 泛型,泛型自动转换为实际类型--本质上上Object -> 实际类型)
     * @return 返回方法的调用结果
     * @throws Exception
     */
    public static <T, R> R invokeMethod(T obj, String methodName) throws Exception {
        Class<?> aClass = obj.getClass();
        Method method = aClass.getDeclaredMethod(methodName);
        return (R) method.invoke(obj);
    }


    /**
     * 6通过反射调用 调用有参方法,返回调用方法的结果
     * 使用反射调用带有参数的成员方法
     *
     * @param obj        调用对象
     * @param methodName 方法名称
     * @param values     值列表 (一个方法里只能有一个参数是参数列表)
     * @param params     参数类型列表
     * @param <T>        支持的泛型
     * @param <R>        泛型类型 支持的泛型
     * @return 返回方法的调用结果 方法调用返回的结果，如果调用的是void方法返回null
     * @throws Exception
     */
    public static <T, R> R invokeMethod(T obj, String methodName, Object[] values, Class... params) throws Exception {
        Class<?> aClass = obj.getClass();
        // 方法名 + 参数类型
        Method method = aClass.getDeclaredMethod(methodName, params);
        return (R) method.invoke(obj, values);
    }


    /**
     * 7暴力破解
     * 访问指定的使用属性,
     * 步骤：1 获取Class对象
     * 2 根据参数名称fieldName获取对应的属性签名对象Field
     * 3 设置Field对象的可访问性为true
     * 4 为属性设置值
     *
     * @param obj       暴力破解需要的对象
     * @param filedName 属性名
     * @param val       破解传入值
     * @param <T>       支持泛型
     */
    public static <T> void accessFiled(T obj, String filedName, Object val) throws Exception {
        // 反射入口
        Class<?> aClass = obj.getClass();
        // 获得属性签名
        Field field = aClass.getDeclaredField(filedName);
        // 核心: 设置可访问
        field.setAccessible(true);
        // set val
        field.set(obj, val);
    }

    /**
     * 8暴力破解某个对象的所有属性
     * 步骤：
     * 1 遍历Map集合
     * 2 获取key和value
     * 3 根据key得到属性签名Field对象
     * 4 设置Field的可访问性为true
     * 5 属性暴力破解
     *
     * @param obj 暴力破解对象
     * @param map 需要破解的对象属性名和对象属性值集合
     * @param <T> 支持泛型
     * @throws Exception
     */
    public static <T> void accessAllFileds(T obj, Map<String, Object> map) throws Exception {
        // 反射入口
        Class<?> aClass = obj.getClass();

        //遍历map
        for (Map.Entry<String, Object> entry : map.entrySet()) {

            String filedName = entry.getKey();
            Object filedValue = entry.getValue();
            Field field = aClass.getDeclaredField(filedName);
            field.setAccessible(true);
            field.set(obj, filedValue);

        }

    }

    /**
     *  9 定义工具方法完成暴力破解
     * ​ 注意：属性暴力破解之前，先判断该属性是否自读，如果是只读不进行暴力破解
     * 注解+反射 进行暴力破解,只读属性不用参加暴力破解
     * @param obj 暴力破解的对象
     * @param map 暴力破解的属性名称和值
     * @param <T>
     * @throws Exception
     */
    public static <T> void accessAllFiledsTwo(T obj, Map<String ,Object> map) throws Exception {
        Class<?> aClass = obj.getClass();

        for (Map.Entry<String, Object> entry : map.entrySet()) {
            // 1获得属性名 key
            String key = entry.getKey();
            // 2获得属性值
            Object value = entry.getValue();
            // 根据属性名称获得属性签名对象Field
            Field field = aClass.getDeclaredField(key);
            // 暴力破解之前,先获得 每个属性签名上面的 注解信息标记
            MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);
            // 表示 readOnly 为真, 为只读属性,不进行暴力破解
            if (annotation.readOnly()){
                continue;
            }
            //不是 就暴力破解
            field.setAccessible(true);
            field.set(obj,value);
        }
    }


}
